package classification.classifier;

import java.io.File;
import java.util.ArrayList;
import org.dom4j.Document;
import org.dom4j.io.SAXReader;

import preprocess.Arith;
import preprocess.DBWrapper;
import preprocess.XMLDoc;
import utils.Configuration;

import classification.calcSimilarity.CalcSimilarityBySLVM;
import classification.elemListRepr.ElemListReprGenerator;
import classification.elemListRepr.XMLElementList;
import classification.geneticAlgorithm.GeneticAlgorithm;
import classification.geneticAlgorithm.GeneticAlgorithmWithLevel;
import classification.geneticAlgorithm.Individual;
import classification.slvm.ElementSimilarityMatrix;
import classification.slvm.StructSimilarity;
import static utils.Configuration.*;

public class ClassifierBySLVM extends Classifier
{
    protected CalcSimilarityBySLVM calcSimilarity = null;
    protected final String         DBName         = Configuration.SIMILARITY.struct_similarity_database;
    protected final String         structSimiID   = Configuration.SIMILARITY.feature_struct_similarity;

    protected int                  geneticTimes   = 0;
    protected int                  elemNum        = 1;

    public ClassifierBySLVM()
    {
        /* 创建similarity计算对象 */
        calcSimilarity = new CalcSimilarityBySLVM();
    }

    public void run()
    {
        try
        {
            /* 初始化得到分类所需要的部分参数和文件等信息 */
            this.init();

            /* 得到与遗传算法相关的参数信息 */
            stdOut.println("Please input Genetic times");
            this.geneticTimes = Integer.parseInt(stdIn.readLine());

            stdOut.println("Please input XML element number you desire");
            this.elemNum = Integer.parseInt(stdIn.readLine());

            /* 调用Cross-Validation程序 */
            double avgPrec = 0.0;
            if ((avgPrec = this.crossValidation()) == -1)
            {
                stdErr.println("Error occur in crossValidation process");
            }
            else
            {
                stdOut.println("The avg precision of C-V is： " + avgPrec);
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }

    protected boolean training(final ArrayList<File> fileArr)
    {
        stdOut.println("Training process is running...");
        try
        {
            /* 找到训练文件的docID */
            this.docIDSet = this.exploreDocID(fileArr);

            // /* 调用遗传算法 */
            // StructSimilarity optSimiMatrix = this.invokeGA(fileArr,
            // this.geneticTimes);
            //
            // if (!this.saveStructSimiMatrix(optSimiMatrix))
            // {
            // stdErr
            // .println("Error occur in save structure similarity matrix");
            // }

            return true;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return false;
        }
    }

    protected double test(final ArrayList<File> fileArr,
            final ArrayList<File> traFileArr)
    {
        stdOut.println("Test process is running...");
        try
        {
            double succ = 0;

            StructSimilarity optSimiMatrix = this.loadStructSimilarity();
            this.calcSimilarity.setStructSimilarity(optSimiMatrix);

            int traFileNum = traFileArr.size();
            int tesFileNum = fileArr.size();

            double[] simiResult = null;
            for (int testIndex = 0; testIndex < tesFileNum; testIndex++)
            {
                simiResult = new double[traFileNum];
                File testFile = fileArr.get(testIndex);
                XMLDoc testXMLDoc = this.getXMLDoc(testFile);

                for (int traIndex = 0; traIndex < traFileNum; traIndex++)
                {
                    File traFile = traFileArr.get(traIndex);
                    XMLDoc traXMLDoc = this.getXMLDoc(traFile);
                    simiResult[traIndex] = this.calcSimilarity.calcSimilarity(
                            testXMLDoc, traXMLDoc);
                }

                /* 找到KNN类别 */
                String category = this.KNNValueClassify(simiResult,
                        this.docIDSet).getCategory();

                /* 判断分类是否正确 */
                if (this.evaluate(testXMLDoc.getDocID(), category))
                {
                    succ++;
//                    stdOut.println(testXMLDoc.getDocID()
//                            + ": is succeed classified to " + category);
                }
                else
                {
                    stdErr.println(testXMLDoc.getDocID()
                            + ": is classified to " + category
                            + ", but desired category is :"
                            + this.categoryMap.get(testXMLDoc.getDocID()));
                }
            }

            double precision = (double) succ / (double) tesFileNum;
            stdOut.println("Test process is end...");
            stdOut.println("\tTest precision is: " + precision);

            return precision;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return -1;
        }
    }

    /**
     * 遗传算法启动程序
     * 
     * @param fileList
     *            待通过遗传算法学习相似度矩阵的XMLElementList数组
     * @param geneticTimes
     *            遗传算法执行的代数
     * @return 通过遗传算法学习到的局部最优相似度矩阵
     */
    private StructSimilarity invokeGA(final ArrayList<File> fileList,
            int geneticTimes)
    {
        double satisfiedRecall = 1.0;
        StructSimilarity optSimiMatrix = null;

        int XMLElemNum = this.elemNum;

        /* 因为这里的相似度矩阵一定是对角阵，所以DNA也就只需记录上三角矩阵即可，所以长度为(n + 1) n / 2 */
        int DNALength = (XMLElemNum + 1) * XMLElemNum / 2;

        /* 创建遗传算法执行对象 */
        GeneticAlgorithm GA = new GeneticAlgorithmWithLevel(DNALength,
                XMLElemNum);

        /* 初始化种群 */
        Individual[] geneticPool = new Individual[GA.getPoolSize()];
        GA.init(geneticPool);

        /* 开始执行遗传算法 */
        Individual maxValueIndv = null;
        int geneticIndex = 0;
        double max = 0.0;
        Individual[] selectPool = null;
        Individual[] crossoverPool = null;
        Individual[] newGeneticPool = null;
        StructSimilarity elemSimiMatrix = null;
        while (geneticIndex++ < this.geneticTimes)
        {
            maxValueIndv = this.calcFitness(fileList, geneticPool);
            stdOut.println("Current genetic times is: " + geneticIndex
                    + "......");
            stdOut.println("\t current max macro recall is: "
                    + maxValueIndv.value);

            /*
             * 调用SLVMSimilarityMatrix的静态方法得到结构相似度矩阵并将其输出
             */
            elemSimiMatrix = this.DNAToStructSimiMatrix(maxValueIndv.DNA,
                    XMLElemNum);

            if (maxValueIndv.value > max)
            {
                optSimiMatrix = elemSimiMatrix;
                max = maxValueIndv.value;
            }

            if (maxValueIndv.value >= satisfiedRecall)
            {
                stdOut
                        .println("**************Succ! The maximum value of current generation is "
                                + maxValueIndv.value
                                + "; and the genetic times is: "
                                + geneticIndex
                                + "**************");
                break;
            }

            /*
             * if max fitness in current generation didn't satisfy the expected
             * value, invoke select, crossover and mutation operations
             */
            selectPool = GA.select(geneticPool);

            /* invoke crossover operation */
            crossoverPool = GA.crossover(geneticPool);

            /* generate new genetic pool */
            newGeneticPool = new Individual[GA.getPoolSize()];
            for (int indvNum = 0; indvNum < selectPool.length; indvNum++)
            {
                newGeneticPool[indvNum] = new Individual(selectPool[indvNum]);
            }
            for (int indvNum = 0; indvNum < crossoverPool.length; indvNum++)
            {
                newGeneticPool[indvNum + selectPool.length] = new Individual(
                        crossoverPool[indvNum]);
            }

            /* invoke mutation operation */
            GA.mutation(newGeneticPool);

            /* assign the newGeneticPool to geneticPool */
            geneticPool = newGeneticPool;
        }
        return optSimiMatrix;
    }

    /**
     * 逐个计算每一个Individual所对应的相似度矩阵适应度值，返回最大适应度值所对应的个体Individual
     * 
     * @param List
     * @param geneticPool
     * @return
     */
    private Individual calcFitness(final ArrayList<File> fileList,
            Individual[] geneticPool)
    {
        Individual bestIndv = new Individual();
        int fileNum = fileList.size();

        try
        {
            /* 逐个计算每一个Individual所对应的适应度值 */
            int XMLElemNum = ((int) Math.sqrt(1 + 8 * geneticPool[0].DNALength) - 1) / 2;
            SAXReader reader = new SAXReader();

            int index = 1;// TODO
            for (Individual indv : geneticPool)
            {
                double start = System.currentTimeMillis();// TODO
                stdOut.println("");

                /* 通过遗传算法中的Individual对象，得到并设置结构相似度矩阵 */
                StructSimilarity structSimilarity = this.DNAToStructSimiMatrix(
                        indv.DNA, XMLElemNum);
                this.calcSimilarity.setStructSimilarity(structSimilarity);

                /*************** 开始计算每个xml与其他xml的相似度, 并进行分类,评测 ***************/
                int succ = 0;
                double[][] simiResult = new double[fileNum][fileNum];
                for (int first = 0; first < fileNum; first++)
                {
                    /* 循环找到每一个xml的类别 */
                    File xml_1 = fileList.get(first);
                    String fileName_1 = xml_1.getName();
                    int partition_1 = fileName_1.lastIndexOf(".");
                    String docID_1 = fileName_1.substring(0, partition_1);
                    Document doc_1 = reader.read(xml_1);
                    XMLDoc xmlDoc_1 = new XMLDoc(doc_1);
                    xmlDoc_1.setDocID(docID_1);

                    for (int second = first + 1; second < fileNum; second++)
                    {
                        File xml_2 = fileList.get(second);
                        String fileName_2 = xml_2.getName();
                        int partition_2 = fileName_2.lastIndexOf(".");
                        String docID_2 = fileName_2.substring(0, partition_2);
                        Document doc_2 = reader.read(xml_2);
                        XMLDoc xmlDoc_2 = new XMLDoc(doc_2);
                        xmlDoc_2.setDocID(docID_2);

                        /* 计算相似度 */
                        simiResult[first][second] = this.calcSimilarity
                                .calcSimilarity(xmlDoc_1, xmlDoc_2);
                        simiResult[second][first] = simiResult[first][second];
                    }

                    /* 找到KNN类别 */
                    String category = this.KNNValueClassify(simiResult[first],
                            this.docIDSet).getCategory();

                    /* 判断分类是否正确 */
                    if (this.evaluate(docID_1, category))
                    {
                        succ++;
                        // System.out.println(docID_1
                        // + " is succeed classified to category "
                        // + category);
                    }
                    else
                    {
                        stdErr.println(docID_1
                                + " is failed classified to category "
                                + category + " but its desire category is "
                                + this.categoryMap.get(docID_1));
                    }
                }

                /* 计算该individual的准确率，并看其是否比最优的高 */
                indv.value = ((double) succ / (double) fileNum);
                stdOut.println("Current individual's precision is "
                        + Arith.round(indv.value, 4));

                if (indv.value > bestIndv.value)
                {
                    bestIndv = new Individual(indv);
                }
                double end = System.currentTimeMillis();// TODO
                stdOut.println(index++
                        + ". time is :"
                        + Arith
                                .round((double) (end - start) / (double) 1000,
                                        4) + ".s");
            }
            return bestIndv;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return null;
        }
    }

    private StructSimilarity DNAToStructSimiMatrix(int[] GA_DNA, int XMLElemNum)
    {
        StructSimilarity Stru_Simi_Matrix = new StructSimilarity();

        int index = 0;
        for (int i = 0; i < XMLElemNum; i++)
        {
            for (int j = i; j < XMLElemNum; j++)
            {
                Stru_Simi_Matrix.addSimilarity(i, j, GA_DNA[index++]);
            }
        }

        for (int i = 0; i < XMLElemNum; i++)
        {
            for (int j = 0; j < i; j++)
            {
                double similarity = Stru_Simi_Matrix.getSimilarity(j, i);
                Stru_Simi_Matrix.addSimilarity(i, j, similarity);
            }
        }
        return Stru_Simi_Matrix;
    }

    private boolean saveStructSimiMatrix(
            final ElementSimilarityMatrix structSimiMatrix)
    {
        DBWrapper db = new DBWrapper();
        structSimiMatrix.setSimiMatrixID(this.structSimiID);

        if (db.exists(this.DBName))
        {
            if (!db.deleteDB(this.DBName)) { return false; }
            if (!db.insert(structSimiMatrix, this.DBName)) { return false; }
        }
        else
        {
            if (!db.insert(structSimiMatrix, this.DBName)) { return false; }
        }
        return true;
    }

    protected StructSimilarity loadStructSimilarity()
    {
        DBWrapper db = new DBWrapper();
        StructSimilarity ss = (StructSimilarity) db.queryElemSimiMatrix(
                this.DBName, this.structSimiID);

        if (ss != null)
        {
            return ss;
        }
        else
        {
            // 如果数据库中没有结构相似度矩阵，则创建相似度矩阵模板返回
            StructSimilarity template = new StructSimilarity();
            template.addSimilarity(0, 0, 1);
            return template;
        }
    }

    protected static void DisplayStructSimiMatrix(
            StructSimilarity structSimiMatrix, XMLElementList templateElemList)
    {
        int elemNum = templateElemList.getElemNum();
        String rowElemName = "";

        stdOut.print("\t");
        for (int i = 0; i < elemNum; i++)
        {
            stdOut.print(templateElemList.get(i).getXMLElemName() + "\t");
        }
        stdOut.println();

        for (int i = 0; i < elemNum; i++)
        {
            rowElemName = templateElemList.get(i).getXMLElemName();
            stdOut.print(rowElemName + "\t");
            for (int j = 0; j < elemNum; j++)
            {
                double similarity = structSimiMatrix.getSimilarity(i, j);
                stdOut.print(similarity + "\t");
            }
            stdOut.println();
        }
    }
}
